<template>
  <div class="container">
    <div class="header">
      <h1>AI对话与知识图谱可视化平台</h1>
      <p></p>
    </div>

    <div class="tabs">
      <div
          class="tab"
          :class="{ active: activeTab === 'ai-chat' }"
          data-tab="ai-chat"
          @click="switchTab('ai-chat')"
      >AI对话分析</div>
      <div
          class="tab"
          :class="{ active: activeTab === 'knowledge-graph' }"
          data-tab="knowledge-graph"
          @click="switchTab('knowledge-graph')"
      >知识图谱可视化</div>
    </div>

    <!-- AI对话区域 -->
    <div id="ai-chat" class="tab-content" :class="{ active: activeTab === 'ai-chat' }">
      <!-- 添加折叠控制按钮 -->
      <div class="section-toggle">
        <button @click="toggleTopSections" class="toggle-btn" :class="{ expanded: areTopSectionsVisible }">
          <span class="toggle-icon"></span>
          <span class="toggle-text">{{ areTopSectionsVisible ? '收起' : '展开' }}</span>
        </button>
      </div>
      <div class="section-container">
        <transition-group name="slide-fade" tag="div">
        <div v-if="areTopSectionsVisible" key="upload-section" class="upload-section">
          <input type="file" id="excelFile" accept=".xlsx,.csv" @change="handleFileSelect" multiple>
          <button id="uploadBtn" @click="uploadExcel">上传数据</button>
        </div>

        <div v-if="areTopSectionsVisible" key="select-section" class="select-section">
          <label for="collectionSelect">选择数据源：</label>
          <select
              id="collectionSelect"
              v-model="selectedCollection"
              :disabled="collectionsLoading"
          >
            <option v-if="collectionsLoading" disabled>加载中...</option>
            <option v-else-if="collections.length === 0" disabled>无可用数据源</option>
            <option
                v-for="col in collections"
                :key="col.id"
                :value="col.id"
            >
              {{ col.name }}
            </option>
          </select>
        </div>
      </transition-group>
      </div>
      <div class="chat-container">
        <div id="chatHistory" class="chat-history">
          <div
              v-for="(message, index) in chatMessages"
              :key="index"
              class="chat-message"
              :class="[message.type + '-message']"
          >
            <div class="message-sender">{{ message.sender }}</div>
            <div class="message-text" v-html="message.text"></div>
            <!--            <div class="message-text">{{ message.text }}</div>-->
          </div>
          <div v-if="isWaiting" class="chat-message ai-message waiting-message">
            <div class="message-sender">AI助手</div>
            <div class="message-text">
              <div class="loading-dots">
                <span></span>
                <span></span>
                <span></span>
              </div>
            </div>
          </div>
        </div>
      </div>

      <div class="input-area">
        <input
            type="text"
            id="questionInput"
            placeholder="请输入问题..."
            v-model="currentQuestion"
            @keyup.enter="askQuestion"
            :disabled="isWaiting"
        >
        <button id="sendBtn" @click="askQuestion" :disabled="isWaiting">
          <span v-if="!isWaiting">发送</span>
          <div v-else class="button-loading"></div>
        </button>
      </div>

    </div>

    <!-- 知识图谱区域 -->
    <div id="knowledge-graph" class="tab-content" :class="{ active: activeTab === 'knowledge-graph' }">

      <!-- 添加折叠控制按钮 -->
      <div class="section-toggle">
        <button @click="topKnowledgeGraphVisible" class="toggle-btn" :class="{ expanded: areTopKnowledgeGraphVisible }">
          <span class="toggle-icon"></span>
          <span class="toggle-text">{{ areTopKnowledgeGraphVisible ? '收起' : '展开' }}</span>
        </button>
      </div>

      <div class="section-container" v-show="areTopKnowledgeGraphVisible">
        <transition-group name="slide-fade" tag="div">
          <div v-if="areTopKnowledgeGraphVisible" key="graph-controls" class="graph-controls-section">
            <div class="control-group">
              <label for="nodeTypeSelect">关系类型：</label>
              <select id="nodeTypeSelect" v-model="selectedNodeType"
                      @change="onCollectionChange">
                <option value="1">结构关系</option>
                <option value="2">文化关系</option>
              </select>
            </div>

            <div class="control-group">
              <label for="layoutSelect">展示数据量：</label>
              <select id="layoutSelect" v-model="dataCount"
                      @change="onCollectionChange">
                <option value="1">少量</option>
                <option value="2">中等</option>
                <option value="3">大量</option>
              </select>
            </div>
          </div>
        </transition-group>
      </div>

      <div class="graph-container">
        <div ref="container" style="width: 100%; height: 100%; border: 1px solid #ccc;"></div>

        <div class="loading-overlay" v-show="graphLoading">
          <div class="loading-container">
            <div class="loading-spinner">
              <div class="spinner-ring"></div>
              <div class="spinner-ring"></div>
              <div class="spinner-ring"></div>
              <div class="spinner-ring"></div>
            </div>
            <div class="loading-text">
              <p>正在加载知识图谱数据...</p>
              <div class="loading-progress">
                <div class="progress-bar"></div>
              </div>
            </div>
          </div>
        </div>

        <div class="node-detail-popup" v-show="showNodeInfo" :style="nodeDetailPosition">
          <div class="node-detail-header">
            <h3>{{ currentNodeInfo.title }}</h3>
            <button class="close-btn" @click="closeNodeDetail">×</button>
          </div>
          <div class="node-detail-content">
            <div class="detail-item" v-for="(value, key) in currentNodeInfo.properties" :key="key">
              <span class="detail-key">{{ key }}:</span>
              <span class="detail-value">{{ value }}</span>
            </div>
          </div>
        </div>
      </div>
    </div>

    <!-- 全屏遮罩loading -->
    <div id="fullscreenLoading" class="fullscreen-loading" v-show="uploading">
      <div class="loading-content">
        <div class="loading-spinner"></div>
        <div class="loading-msg">正在上传文件，请稍候...</div>
      </div>
    </div>
  </div>
</template>
<script>
import {Graph, NodeEvent} from '@antv/g6';

let graph = null;
export default {
  name: 'TombsView',
  data() {
    return {
      // basePath: '',
      basePath: 'http://localhost:6688',
      activeTab: 'ai-chat',
      currentQuestion: '',
      chatMessages: [],
      selectedFiles: null,
      uploading: false,
      isWaiting: false,
      collections: [],               // 存储从接口获取的集合列表
      selectedCollection: '',        // 当前选中的集合 ID 或名称
      collectionsLoading: false,     // 可选：加载状态（用于显示 loading）
      areTopSectionsVisible: false,
      areTopKnowledgeGraphVisible: false,
      nodes: [],
      edges: [],
      graphLoading: false,
      showNodeInfo: false,
      currentNodeInfo: {
        title: '',
        properties: {}
      },
      nodeDetailPosition: {
        left: '0px',
        top: '0px'
      },
      nodeTypeColors: {
        'default': '#4e54c8',
        'Garment': '#45B7D1',
        'TypeName': '#DDA0DD',
        'CulturalFactor': '#cff30e',
      },
      selectedNodeType: 1,
      dataCount: "1",
      lastSelectedNodeType: 1,
      lastDataCount: "1",
    };
  },
  mounted: function () {
    this.loadCollections();
  },
  methods: {
    switchTab(tabName) {
      this.activeTab = tabName;
      if (tabName === 'knowledge-graph') {
        this.$nextTick(() => {
          // 只有当图存在且尚未加载数据时，才初始化
          if (!this.graphLoading) {
            this.initGraph();
          }
        });
      }
    },

    handleFileSelect(event) {
      this.selectedFiles = Array.from(event.target.files);
    },

    uploadExcel() {
      if (!this.selectedFiles) return;
      this.uploading = true;
      const formData = new FormData();
      this.selectedFiles.forEach((file, index) => {
        formData.append(`file${index}`, file);
      });
      fetch(this.basePath+'/api/upload', {
        method: 'POST',
        body: formData
      })
          .then(response => {
            if (response.ok) {
              this.addMessage('系统', `成功上传 ${this.selectedFiles.length} 个文件！`, 'ai');
              // 清空已选择的文件列表
              this.selectedFiles = [];
              // 清空文件输入框
              document.getElementById('excelFile').value = '';
            } else {
              throw new Error('Upload failed');
            }
          })
          .catch(error => {
            this.addMessage('系统', '上传失败: ' + error.message, 'ai');
          })
          .finally(() => {
            this.uploading = false;
          });
    },

    askQuestion() {
      const collectionId = this.selectedCollection;
      if (!collectionId) {
        this.addMessage('系统', '请选择一个集合！', 'ai');
        return;
      }

      const question = this.currentQuestion.trim();
      if (!question) return;

      this.addMessage('用户', question, 'user');
      this.currentQuestion = '';
      this.isWaiting = true;

      fetch(this.basePath + '/api/ask', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },

        body: JSON.stringify({ question, collection: collectionId })
      })
          .then(response => response.json())
          .then(data => {
            this.addMessage('AI助手', data.answer, 'ai');
          })
          .catch(error => {
            this.addMessage('AI助手', '请求失败: ' + error.message, 'ai');
          })
          .finally(() => {
            this.isWaiting = false;
          });
    },

    addMessage(sender, text, type) {
      // Step 1: 过滤掉 <think>...</think> 及其内容
      // let cleanedText = text.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
      let cleanedText = text.replace(/^[\s\S]*?<\/think>/, '').trim();
      // Step 2: 转义 HTML 并转换简单 Markdown 为 HTML
      cleanedText = this.markdownToHtml(cleanedText);

      this.chatMessages.push({
        sender,
        text: cleanedText,  // 注意：这里存的是 HTML 字符串
        type
      });

      // 滚动到底部
      this.$nextTick(() => {
        const history = document.getElementById('chatHistory');
        if (history) history.scrollTop = history.scrollHeight;
      });
    },

    markdownToHtml(text) {
      if (!text) return '';

      return text
          // 转义 HTML 特殊字符（防 XSS）
          .replace(/&/g, '&amp;')
          .replace(/</g, '&lt;')
          .replace(/>/g, '&gt;')
          .replace(/"/g, '&quot;')
          .replace(/'/g, '&#x27;')
          // 标题
          .replace(/^\s*###\s+(.+)$/gm, '<h3>$1</h3>')
          .replace(/^\s*##\s+(.+)$/gm, '<h2>$1</h2>')
          .replace(/^\s*#\s+(.+)$/gm, '<h1>$1</h1>')
          // 加粗
          .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
          // 无序列表
          .replace(/^\s*-\s+(.+)$/gm, '<li>$1</li>')
          // 包裹 ul
          .replace(/(<li>.*?<\/li>)/gs, '<ul>$1</ul>')
          // 换行
          .replace(/\n/g, '<br>')
          // 分割线
          .replace(/^\s*---+$/gm, '<hr>')
          // 清理多余的 br
          .replace(/<br><ul>/g, '<ul>')
          .replace(/<\/ul><br>/g, '</ul>');
    },

    async initGraph() {
      this.graphLoading = true;
      try {
        if (graph) {
          try {
            graph.off('node:click');
            graph.destroy();
          } catch (e) {
            console.warn('Error destroying existing graph:', e);
          }
        }

        const nodeType = this.selectedNodeType;
        const dataCount = this.dataCount;
        let nodeCount = 5;
        let edgeCount = 10;
        switch (dataCount) {
          case "1":
            nodeCount = 5;
            edgeCount = 10;
            break;
          case "2":
            nodeCount = 10;
            edgeCount = 20;
            break;
          default:
            nodeCount = 50;
            edgeCount = 100;
            break;
        }
        const response = await fetch(`${this.basePath}/api/getFullGraph?runType=${nodeType}&nodeCount=${nodeCount}&edgeCount=${edgeCount}`);
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const graphData = await response.json();
        this.nodes = graphData.nodes;
        this.edges = graphData.edges;
        const data = {
          nodes: this.nodes.map(node => {
            return {
              id: node.id,
              style: {
                type: 'node',
                size: 40,
                fill: this.nodeTypeColors[node.type],
              },
              data: {
                ...node,
                _type: 'node'
              }
            };
          }),
          edges: this.edges.map(edge => ({
            id: `${edge.source}-${edge.target}`,
            source: edge.source,
            target: edge.target,
            style: {
              labelText: edge.label || '',
              labelFontSize: 10,
              labelFill: '#000',
            },
            data: {
              ...edge,
              _type: 'edge'
            }
          }))
        }

        const container = this.$refs.container;
        // 创建图实例
        graph = new Graph({
          container: container,
          data,
          width: container.offsetWidth,
          height: container.offsetHeight,
          autoResize: true,
          layout: {
            type: 'd3-force',
            gravity: 0.3,             // 增加重力参数，让节点更分散
            speed: 5,                // 调整布局速度
            animation: true,
            preventOverlap: true,    // 防止节点重叠
            link: { // 边的配置（替换原 linkDistance 和 edgeStrength）
              distance: 200,
              strength: 0.3 // 边的拉力
            },
            nodeStrength: -300,       // 增强节点间斥力（原来-30）
            edgeStrength: 0.1,       // 减少边的束缚力
            collideStrength: 1,      // 碰撞检测强度
            alpha: 1,                // 保持初始布局能量
            alphaDecay: 0.05,        // 能量衰减速度
            alphaMin: 0.01,          // 最小能量阈值
          },
          nodeStateStyles: {
            hover: {
              fill: '#c6e4ff',
              stroke: '#5B8FF9'
            }
          },
          behaviors: [
            'drag-canvas',
            'zoom-canvas',
            'drag-element'
          ],
          plugins: [
            function () {
              return {
                key: 'tooltip-click',
                type: 'tooltip',
                trigger: 'click',
                getContent: (e,items) => {
                  const item = items[0];
                  if (item.data._type === 'node') {
                    let content = '<div class="g6-tooltip">';
                    const label = item.data.label;
                    const properties = item.data.properties || {};
                    content += `<h4>${label}</h4>`;
                    Object.keys(properties).forEach(key => {
                      content += `<p><strong>${key}:</strong> ${properties[key]}</p>`;
                    });
                    content += '</div>';
                    return content;
                  } else {
                    return '';
                  }
                },
                onOpenChange: (open) => {
                  const tooltip = this.getPluginInstance('tooltip-hover');
                  if (tooltip && open) tooltip.hide();
                },
              };
            },
            function () {
              return {
                key: 'tooltip-hover',
                type: 'tooltip',
                trigger: 'hover',
                getContent: (e,items) => {
                  const item = items[0];
                  if (item.data._type === 'node') {
                    let content = '<div class="g6-tooltip">';
                    const label = item.data.label;
                    const properties = item.data.properties || {};
                    content += `<h4>${label}</h4>`;
                    Object.keys(properties).forEach(key => {
                      content += `<p><strong>${key}:</strong> ${properties[key]}</p>`;
                    });
                    content += '</div>';
                    return content;
                  } else {
                    return '';
                  }
                },
                onOpenChange: (open) => {
                  const tooltip = this.getPluginInstance('tooltip-click');
                  if (tooltip && open) {
                    tooltip.hide();
                  }
                },
              }
            },
          ],
        });
        await graph.render();
      }  catch (error) {
        console.error('获取图数据失败:', error);
        // 可以添加用户提示
        this.addMessage('系统', '获取知识图谱数据失败: ' + error.message, 'ai');
      } finally {
        this.graphLoading = false;
      }

    },

    showNodeDetails(node, event) {
      // 获取节点数据
      const nodeData = node.getData();

      // 设置要显示的节点详情
      this.currentNodeInfo.title = nodeData.label || '未命名节点';
      this.currentNodeInfo.properties = {};

      // 过滤掉不需要显示的属性
      Object.keys(nodeData).forEach(key => {
        if (!['id', 'label', 'x', 'y', 'type', '_type'].includes(key)) {
          this.currentNodeInfo.properties[key] = nodeData[key];
        }
      });

      // 计算浮窗位置
      const containerRect = this.$refs.container.getBoundingClientRect();
      this.nodeDetailPosition = {
        left: (event.canvasX || event.x || 0) + containerRect.left + 20 + 'px',
        top: (event.canvasY || event.y || 0) + containerRect.top + 20 + 'px'
      };

      // 显示浮窗
      this.showNodeInfo = true;
    },

    closeNodeDetail() {
      this.showNodeInfo = false;
    },

    bindGraphEvents(){
      graph.on(NodeEvent.click, (event) => {
        const node = event.Item || event.item;
        if (node) {
          this.showNodeDetails(node, event);
        }
      });

      graph.on(NodeEvent.DragEnd, (event) => {
        const node = event.Item || event.item;
        if (node) {
          this.showNodeDetails(node, event);
        }
      });

    },

    async loadCollections() {
      this.collectionsLoading = true;
      try {
        const response = await fetch(this.basePath + '/api/getCollections');
        if (!response.ok) {
          throw new Error('Failed to fetch collections');
        }
        const data = await response.json();
        const collections = data.collections
        const names = Array.isArray(collections) ? collections : [];
        this.collections = names.map(name => ({
          id: name,
          name: name
        }));
        if (this.collections.length > 0) {
          this.selectedCollection = this.collections[0].id; // 默认选第一个
        }
      } catch (error) {
        this.collections = [];
      } finally {
        this.collectionsLoading = false;
      }
    },

    onCollectionChange() {
      // 如果图正在加载，则恢复到原来的值
      if (this.graphLoading) {
        this.$nextTick(() => {
          this.selectedNodeType = this.lastSelectedNodeType;
          this.dataCount = this.lastDataCount;
        });
        return;
      }

      // 更新上一次的值
      this.lastSelectedNodeType = this.selectedNodeType;
      this.lastDataCount = this.dataCount;

      // 初始化图
      this.initGraph();
    },

    toggleTopSections() {
      this.areTopSectionsVisible = !this.areTopSectionsVisible;
    },

    topKnowledgeGraphVisible() {
      this.areTopKnowledgeGraphVisible = !this.areTopKnowledgeGraphVisible;
    },


  },
  beforeUnmount (){
    if (graph) {
      graph.destroy();
      graph = null;
    }
  }
};
</script>

<style scoped>
/* 基础样式 */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

body {
  background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
  min-height: 100vh;
  padding: 10px;
  color: #333;
  margin-top: -55px;
}

.container {
  width: 100%;
  max-width: 1400px;
  margin: 0 auto;
  background: white;
  border-radius: 20px;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
  overflow: hidden;
}

/* 头部样式 */
.header {
  background: linear-gradient(135deg, #4e54c8, #8f94fb);
  color: white;
  padding: 10px 15px;
  text-align: center;
  position: relative;
  overflow: hidden;
}

.header::before {
  content: "";
  position: absolute;
  top: -50%;
  left: -50%;
  width: 200%;
  height: 200%;
  background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 70%);
  transform: rotate(30deg);
}

.header h1 {
  font-size: 2em;
  margin-bottom: 2px;
  font-weight: 300;
  text-shadow: 0 2px 4px rgba(0,0,0,0.2);
  position: relative;
  letter-spacing: 1px;
  animation: fadeInDown 1s ease-out;
}

.header p {
  font-size: 1em;
  opacity: 0.9;
  position: relative;
  max-width: 800px;
  margin: 0 auto;
  line-height: 1.4;
}

@keyframes fadeInDown {
  from {
    opacity: 0;
    transform: translateY(-20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* 选项卡样式 */
.tabs {
  display: flex;
  background: #f8f9fa;
  border-bottom: 1px solid #e9ecef;
}

.tab {
  padding: 15px 30px;
  cursor: pointer;
  font-weight: 600;
  transition: all 0.3s ease;
  border-bottom: 3px solid transparent;
}

.tab.active {
  background: white;
  border-bottom: 3px solid #4e54c8;
  color: #4e54c8;
}

.tab:hover:not(.active) {
  background: rgba(78, 84, 200, 0.1);
}

/* 内容区域样式 */
.tab-content {
  width: 100%;
  display: none;
  padding: 0;
}

.tab-content.active {
  width: 100%;
  display: block;
}

/* AI对话区域样式 */
.chat-container {
  display: flex;
  flex-direction: column;
  height: 600px;
  position: relative;
}

/* 添加容器包装 input-area */
.input-area-container {
  position: relative;
  width: 100%;
}

.chat-history {
  flex: 1;
  padding: 20px;
  overflow-y: auto;
  background: #f9f9f9;
}

.chat-message {
  margin-bottom: 15px;
  padding: 12px 18px;
  border-radius: 18px;
  max-width: 80%;
  animation: fadeIn 0.3s ease;
}

.user-message {
  background: #e3f2fd;
  margin-left: auto;
  border-bottom-right-radius: 5px;
}

.ai-message {
  background: #4e54c8;
  color: white;
  border-bottom-left-radius: 5px;
}

.input-area {
  display: flex;
  padding: 15px;
  background: white;
  border-top: 1px solid #eee;
  position: relative;
  width: 100%;
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
}

input-area input {
  flex: 1;
  background: #f5f7fa;
  margin-right: 10px;
  border: 2px solid #eef2f7;
  border-radius: 30px;
  padding: 12px 15px;
  font-size: 16px;
  transition: border-color 0.3s;
}

.input-area input:focus {
  outline: none;
  border-color: #4e54c8;
  box-shadow: 0 0 0 2px rgba(78, 84, 200, 0.2);
}

.input-area button {
  background: linear-gradient(to right, #4e54c8, #8f94fb);
  color: white;
  cursor: pointer;
  font-weight: 600;
  transition: all 0.3s ease;
  min-width: 80px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 30px;
  padding: 12px 15px;
  font-size: 16px;
}

.input-area button:hover:not(:disabled) {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(78, 84, 200, 0.3);
}

.input-area button:disabled {
  opacity: 0.7;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

/* 悬浮 input-area 样式 */
.input-area.floating-input {
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  width: calc(100% - 40px);
  max-width: 800px;
  background: white;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  border-radius: 30px;
  z-index: 10;
  display: flex;
  padding: 10px 15px;
  border: 1px solid #eef2f7;
}


.input-area.floating-input input {
  flex: 1;
  background: #f5f7fa;
  margin-right: 10px;
  border: none;
  border-radius: 30px;
  padding: 12px 15px;
  font-size: 16px;
}

.input-area.floating-input input:focus {
  outline: none;
  border-color: #4e54c8;
  box-shadow: 0 0 0 2px rgba(78, 84, 200, 0.2);
}

.input-area.floating-input button {
  background: linear-gradient(to right, #4e54c8, #8f94fb);
  color: white;
  cursor: pointer;
  font-weight: 600;
  transition: all 0.3s ease;
  min-width: 80px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 30px;
  padding: 12px 15px;
  font-size: 16px;
}

.input-area.floating-input button:hover:not(:disabled) {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(78, 84, 200, 0.3);
}

.input-area.floating-input button:disabled {
  opacity: 0.7;
  cursor: not-allowed;
}

input, button {
  padding: 12px 15px;
  border: none;
  border-radius: 30px;
  font-size: 16px;
}

#questionInput {
  flex: 1;
  background: #f5f7fa;
  margin-right: 10px;
}

button {
  background: linear-gradient(to right, #4e54c8, #8f94fb);
  color: white;
  cursor: pointer;
  font-weight: 600;
  transition: transform 0.2s;
}

button:hover {
  transform: translateY(-2px);
}

/* 知识图谱区域样式 */
.graph-container {
  width: 100%;
  height: 600px;
  position: relative;
  background: #fff;
}

.control-group label {
  font-weight: 600;
  color: #495057;
}

select, input {
  padding: 10px 15px;
  border: 2px solid #e9ecef;
  border-radius: 10px;
  font-size: 14px;
  transition: all 0.3s ease;
}

select:focus, input:focus {
  outline: none;
  border-color: #4e54c8;
  box-shadow: 0 0 0 3px rgba(78, 84, 200, 0.1);
}

.legend {
  position: absolute;
  top: 20px;
  right: 20px;
  background: rgba(255, 255, 255, 0.95);
  padding: 15px;
  border-radius: 10px;
  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
  border: 1px solid #e9ecef;
}

.legend-item {
  display: flex;
  align-items: center;
  margin: 8px 0;
  font-size: 12px;
}

.legend-color {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  margin-right: 8px;
}

.node-info h3 {
  color: #4e54c8;
  margin-bottom: 10px;
  border-bottom: 2px solid #4e54c8;
  padding-bottom: 5px;
}

.loading {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  color: #6c757d;
}

.loading-spinner {
  border: 3px solid #f3f3f3;
  border-top: 3px solid #4e54c8;
  border-radius: 50%;
  width: 40px;
  height: 40px;
  animation: spin 1s linear infinite;
  margin: 0 auto 10px;
}

/* 动画 */
@keyframes fadeIn {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* 上传区域样式 */
.upload-section {
  padding: 20px;
  display: flex;
  gap: 10px;
  background: #f8f9fa;
  border-bottom: 1px solid #e9ecef;
}

/* 全屏遮罩loading */
.fullscreen-loading {
  display: flex;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.6);
  z-index: 9999;
  justify-content: center;
  align-items: center;
  backdrop-filter: blur(2px);
}

.fullscreen-loading .loading-content {
  background: white;
  padding: 40px 50px;
  border-radius: 15px;
  text-align: center;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
  animation: popup 0.3s ease-out;
}

@keyframes popup {
  0% { transform: scale(0.8); opacity: 0; }
  100% { transform: scale(1); opacity: 1; }
}

@keyframes fadeOut {
  0% { opacity: 1; }
  100% { opacity: 0; }
}

.loading-msg {
  margin-top: 20px;
  font-size: 18px;
  color: #333;
  font-weight: 500;
}

.checkbox-item input[type="checkbox"] {
  width: 18px;
  height: 18px;
  accent-color: #4e54c8;
  cursor: pointer;
}

.checkbox-item label {
  font-weight: 500;
  color: #495057;
  cursor: pointer;
  user-select: none;
}

.checkbox-item input[type="checkbox"]:checked + label {
  color: #4e54c8;
  font-weight: 600;
}

/* 节点详情浮窗样式 */
.node-detail-popup {
  position: absolute;
  background: white;
  border-radius: 8px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
  z-index: 1000;
  min-width: 250px;
  max-width: 400px;
  max-height: 300px;
  overflow-y: auto;
  border: 1px solid #e0e0e0;
}

.node-detail-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 15px;
  border-bottom: 1px solid #eee;
  background: #f8f9fa;
  border-radius: 8px 8px 0 0;
}

.node-detail-header h3 {
  margin: 0;
  font-size: 16px;
  color: #333;
  font-weight: 600;
}

.close-btn {
  background: none;
  border: none;
  font-size: 20px;
  cursor: pointer;
  color: #999;
  padding: 0;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.close-btn:hover {
  color: #666;
}

.node-detail-content {
  padding: 15px;
}

.detail-item {
  margin-bottom: 10px;
  display: flex;
  flex-wrap: wrap;
}

.detail-key {
  font-weight: 600;
  color: #555;
  min-width: 80px;
  margin-right: 10px;
}

.detail-value {
  flex: 1;
  color: #333;
  word-break: break-word;
}

.chat-message {
  margin-bottom: 15px;
  padding: 12px 18px;
  border-radius: 18px;
  max-width: 80%;
  animation: fadeIn 0.3s ease;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}

.user-message {
  background: linear-gradient(135deg, #4e54c8, #8f94fb);
  color: white;
  margin-left: auto;
  border-bottom-right-radius: 5px;
}

.ai-message {
  background: #f0f2f5;
  color: #333;
  border-bottom-left-radius: 5px;
}

.message-sender {
  font-weight: bold;
  margin-bottom: 5px;
  font-size: 0.9em;
  display: flex;
  align-items: center;
}

.user-message .message-sender {
  color: rgba(255, 255, 255, 0.9);
}

.ai-message .message-sender {
  color: #4e54c8;
}

.message-sender::after {
  content: ":";
  margin-left: 2px;
}

.message-text {
  line-height: 1.4;
  padding-left: 2px;
}

.waiting-message {
  display: flex;
  align-items: center;
}

.loading-dots {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 20px;
}

.loading-dots span {
  width: 8px;
  height: 8px;
  background-color: #4e54c8;
  border-radius: 50%;
  margin: 0 3px;
  animation: bounce 1.5s infinite;
}

.loading-dots span:nth-child(2) {
  animation-delay: 0.2s;
}

.loading-dots span:nth-child(3) {
  animation-delay: 0.4s;
}

.input-area {
  display: flex;
  padding: 15px;
  background: white;
  border-top: 1px solid #eee;
}

input, button {
  padding: 12px 15px;
  border: none;
  border-radius: 30px;
  font-size: 16px;
}

#questionInput {
  flex: 1;
  background: #f5f7fa;
  margin-right: 10px;
  border: 2px solid #eef2f7;
  transition: border-color 0.3s;
}

#questionInput:focus {
  outline: none;
  border-color: #4e54c8;
  box-shadow: 0 0 0 2px rgba(78, 84, 200, 0.2);
}

button {
  background: linear-gradient(to right, #4e54c8, #8f94fb);
  color: white;
  cursor: pointer;
  font-weight: 600;
  transition: all 0.3s ease;
  min-width: 80px;
  display: flex;
  align-items: center;
  justify-content: center;
}

button:hover:not(:disabled) {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(78, 84, 200, 0.3);
}

button:disabled {
  opacity: 0.7;
  cursor: not-allowed;
}

.button-loading {
  width: 20px;
  height: 20px;
  border: 3px solid rgba(255, 255, 255, 0.3);
  border-top: 3px solid white;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes bounce {
  0%, 100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-5px);
  }
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.chat-message.ai-message .message-text {
  line-height: 1.8;
  font-size: 14px;
}

.chat-message.ai-message .message-text h1,
.chat-message.ai-message .message-text h2,
.chat-message.ai-message .message-text h3 {
  margin: 0.8em 0 0.5em;
  color: #2c3e50;
}

.chat-message.ai-message .message-text h1 {
  font-size: 18px;
  border-bottom: 1px solid #ddd;
  padding-bottom: 4px;
}

.chat-message.ai-message .message-text h2 {
  font-size: 16px;
  color: #3b5998;
}

.chat-message.ai-message .message-text h3 {
  font-size: 15px;
  color: #1a1a1a;
}

.chat-message.ai-message .message-text ul {
  margin: 0.5em 0;
  padding-left: 20px;
}

.chat-message.ai-message .message-text li {
  margin: 0.3em 0;
}

.chat-message.ai-message .message-text strong {
  color: #1a1a1a;
  font-weight: 600;
}

.chat-message.ai-message .message-text hr {
  border: 1px dashed #ddd;
  margin: 15px 0;
}

/* 下拉选择区域 */
.select-section {
  padding: 15px 20px;
  display: flex;
  align-items: center;
  gap: 12px;
  background: #f8f9fa;
  border-bottom: 1px solid #e9ecef;
}

.select-section label {
  font-weight: 600;
  color: #495057;
  font-size: 15px;
}

.select-section select {
  padding: 10px 16px;
  border: 2px solid #e9ecef;
  border-radius: 12px;
  font-size: 15px;
  background: white;
  color: #333;
  outline: none;
  transition: all 0.3s ease;
  min-width: 180px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
}

.select-section select:focus {
  border-color: #4e54c8;
  box-shadow: 0 0 0 3px rgba(78, 84, 200, 0.15);
}

.select-section select:hover {
  border-color: #8f94fb;
}
.loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(255, 255, 255, 0.85);
  backdrop-filter: blur(4px);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.loading-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 30px 40px;
  background: white;
  border-radius: 16px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
  min-width: 300px;
}

.loading-spinner {
  position: relative;
  width: 60px;
  height: 60px;
  margin-bottom: 20px;
}

.spinner-ring {
  position: absolute;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  border: 4px solid transparent;
  border-top-color: #4e54c8;
  animation: spin 1.5s cubic-bezier(0.5, 0.3, 0.5, 0.7) infinite;
}

.spinner-ring:nth-child(1) {
  animation-delay: -0.4s;
  border-top-color: #4e54c8;
}

.spinner-ring:nth-child(2) {
  animation-delay: -0.3s;
  border-top-color: #8f94fb;
}

.spinner-ring:nth-child(3) {
  animation-delay: -0.2s;
  border-top-color: #c3cfe2;
}

.spinner-ring:nth-child(4) {
  animation-delay: -0.1s;
  border-top-color: #f5f7fa;
}

.loading-text p {
  margin: 0;
  font-size: 16px;
  font-weight: 500;
  color: #495057;
  text-align: center;
  margin-bottom: 15px;
}

.loading-progress {
  width: 100%;
  height: 6px;
  background: #e9ecef;
  border-radius: 3px;
  overflow: hidden;
}

.progress-bar {
  width: 60%;
  height: 100%;
  background: linear-gradient(to right, #4e54c8, #8f94fb);
  border-radius: 3px;
  animation: progress 1.5s ease-in-out infinite;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

@keyframes progress {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(100%);
  }
}

.section-toggle {
  padding: 8px 20px;
  background: #f8f9fa;
  border-bottom: 1px solid #e9ecef;
  text-align: center;
}

.toggle-btn {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 20px;
  cursor: pointer;
  font-weight: 600;
  font-size: 14px;
  transition: all 0.3s ease;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
  position: relative;
  overflow: hidden;
}

.toggle-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.toggle-btn:active {
  transform: translateY(0);
}

.toggle-btn::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(255, 255, 255, 0.1);
  transform: translateX(-100%);
  transition: transform 0.3s ease;
}

.toggle-btn:hover::before {
  transform: translateX(0);
}

.toggle-icon {
  display: inline-block;
  width: 16px;
  height: 16px;
  position: relative;
  transition: transform 0.3s ease;
}

.toggle-icon::before,
.toggle-icon::after {
  content: "";
  position: absolute;
  background: white;
  border-radius: 2px;
  transition: all 0.3s ease;
}

.toggle-icon::before {
  width: 16px;
  height: 2px;
  top: 7px;
  left: 0;
}

.toggle-icon::after {
  width: 2px;
  height: 16px;
  top: 0;
  left: 7px;
}

.toggle-btn.expanded .toggle-icon::after {
  transform: rotate(90deg);
  left: 7px;
}

.toggle-text {
  position: relative;
  z-index: 1;
}


/* 滑动淡入淡出动画 */
.slide-fade-enter-active {
  transition: all 0.3s ease;
}

.slide-fade-enter-active,
.slide-fade-leave-active {
  transition: all 0.3s ease;
  position: absolute;
  width: 100%;
}

.slide-fade-enter-from,
.slide-fade-leave-to {
  transform: translateY(-20px);
  opacity: 0;
}

.slide-fade-enter-to,
.slide-fade-leave-from {
  transform: translateY(0);
  opacity: 1;
}

/* 为包含 transition-group 的容器添加相对定位 */
.section-container {
  background: #f8f9fa;
  position: relative;
  min-height: 60px; /* 根据实际内容调整 */
}

/* 为折叠区域添加独立的过渡效果 */
.upload-section,
.select-section {
  transition: all 0.3s ease;
}

.g6-tooltip {
  border: 1px solid #e2e2e2;
  border-radius: 4px;
  font-size: 12px;
  color: #545454;
  background-color: rgba(255, 255, 255, 0.9);
  padding: 10px 8px;
  box-shadow: rgb(174, 174, 174) 0px 0px 10px;
  max-width: 300px;
  word-wrap: break-word;
}

.g6-tooltip h4 {
  margin: 0 0 5px 0;
  color: #4e54c8;
  font-size: 14px;
}

.g6-tooltip p {
  margin: 0;
  line-height: 1.8;
}

.g6-tooltip p strong {
  margin-right: 5px;
}

.graph-controls-toggle {
  padding: 8px 20px;
  background: #f8f9fa;
  border-bottom: 1px solid #e9ecef;
  text-align: center;
}

.graph-controls-container {
  position: relative;
  background: #f8f9fa;
  min-height: 60px;
}

.graph-controls-section {
  display: flex;
  gap: 20px;
  padding: 15px 20px;
  background: #f8f9fa;
  border-bottom: 1px solid #e9ecef;
}

.graph-controls-section .control-group {
  display: flex;
  align-items: center;
  gap: 10px;
}

.graph-controls-section .control-group label {
  font-weight: 600;
  color: #495057;
  font-size: 14px;
  white-space: nowrap;
}

.graph-controls-section .control-group select {
  padding: 8px 12px;
  border: 2px solid #e9ecef;
  border-radius: 8px;
  font-size: 14px;
  background: white;
  color: #333;
  outline: none;
  transition: all 0.3s ease;
  min-width: 120px;
}

.graph-controls-section .control-group select:focus {
  border-color: #4e54c8;
  box-shadow: 0 0 0 3px rgba(78, 84, 200, 0.15);
}

.graph-controls-section .control-group select:hover {
  border-color: #8f94fb;
}

/* 知识图谱区域的 slide-fade 动画 */
.graph-controls-container .slide-fade-enter-active,
.graph-controls-container .slide-fade-leave-active {
  transition: all 0.3s ease;
  position: absolute;
  width: 100%;
}

.graph-controls-container .slide-fade-enter-from,
.graph-controls-container .slide-fade-leave-to {
  transform: translateY(-20px);
  opacity: 0;
}

.graph-controls-container .slide-fade-enter-to,
.graph-controls-container .slide-fade-leave-from {
  transform: translateY(0);
  opacity: 1;
}

</style>